package com.gitee.kenewstar.common;

import static com.gitee.kenewstar.utils.ReflectUtil.withAnnotation;

import com.gitee.kenewstar.utils.AssertUtil;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * @author xinke.huang@hand-china.com
 * @date 2022/2/22
 */
public class ClassPathScanner {

	private static final Logger LOG = Logger.getLogger(ClassPathScanner.class.getName());

	/**
	 * 常量维护
	 */
	public interface Const {
		String CLASS_SUFFIX = ".class";
		String BLANK = "";
		String SPOT = ".";
	}

	public static ClassPathScanner createScanner() {
		return new ClassPathScanner();
	}

	protected ClassLoader classLoader() {
		return Thread.currentThread().getContextClassLoader();
	}

	protected String pathToPackage(String pathName) {
		return pathName.replace(File.separator, Const.SPOT);
	}

	protected String packageToPath(String packageName) {
		return packageName.replace(Const.SPOT, File.separator);
	}

	protected Class<?> loadClass(String className) {
		try {
			return Class.forName(className);
		} catch (ClassNotFoundException e) {
			LOG.warning(e.getMessage() + " not found");
		}
		return null;
	}

	protected Class<?> resolveClass(String fileName) {
		if (fileName.endsWith(Const.CLASS_SUFFIX)) {
			String pathName = fileName.replace(Const.CLASS_SUFFIX, Const.BLANK);
			return this.loadClass(pathToPackage(pathName));
		}
		return null;
	}


	protected void scanDirectory(File[] files, String superPath, Set<String> fileNames) {
		if (Objects.isNull(files) || files.length == 0) {
			return;
		}
		for (File file : files) {
			String currentPath;
			if (superPath.isEmpty()) {
				currentPath = superPath + file.getName();
			} else {
				currentPath = superPath + File.separator + file.getName();
			}
			if (file.isDirectory()) {
				scanDirectory(file.listFiles(), currentPath, fileNames);
			} else {
				fileNames.add(currentPath);
			}
		}
	}

	protected Set<String> scanFile(String directory){
		String rootPath = Objects.isNull(directory) ? Const.BLANK : directory;
		Set<String> fileList = new HashSet<>();

		try {
			URL url = this.classLoader().getResource(rootPath);
			if (Objects.isNull(url)) {
				return fileList;
			}
			File classpathRoot = new File(url.toURI());
			scanDirectory(classpathRoot.listFiles(), rootPath, fileList);
		} catch (Exception e) {
			AssertUtil.throwException(e);
		}

		return fileList;
	}


	public Set<Class<?>> scanPackage(String packageName) {
		long scanStart = System.currentTimeMillis();

		Set<Class<?>> classSet = this.scanFile(packageToPath(packageName))
				.stream()
				.map(this::resolveClass)
				.filter(Objects::nonNull)
				.collect(Collectors.toSet());

		LOG.info("scan class time ===> " + (System.currentTimeMillis() - scanStart) + " ms");
		LOG.info("scan class count ===> " + classSet.size());
		return classSet;
	}

	public Set<Class<?>> scanPackage() {
		return this.scanPackage(Const.BLANK);
	}

	public Set<Class<?>> getTypesWithAnnotation(Class<? extends Annotation> annotationType) {
		return this.scanPackage().stream()
				.filter(withAnnotation(annotationType))
				.collect(Collectors.toSet());
	}

}
